package org.hackystat.devcathlon.event.impl;

import javax.xml.datatype.XMLGregorianCalendar;

import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.dailyprojectdata.resource.coverage.jaxb.CoverageDailyProjectData;
import org.hackystat.devcathlon.event.EventException;
import org.hackystat.devcathlon.event.EventScore;
import org.hackystat.devcathlon.event.Event;
import org.hackystat.devcathlon.match.Match;
import org.hackystat.devcathlon.team.Team;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * 
 * Reward teams that improve their coverage.  // TODO No test case.  
 * <p>
 * Requires the following properties in the configuration.xml file for this event:
 * <ul>
 * <li> WakeupInterval: How frequently this event is invoked, and how far back it looks for data.
 * <li> BaseScoreWeight: An integer multiplied by the base score to get the base points.  
 * <li> TypeOfCoverage: A string indicating the kind of coverage to retrieve, such as "Line". 
 * <li> PercentImprovement: An integer indicating the minimum amount of improvement to 
 * recieve/lose points.
 * <li> BonusPercentImprovement: An integer indicating the minimum amount of improvement 
 * to get bonus.
 * <li> BonusImprovedDays: An integer for the number of days of improvements
 * <li> BonusScoreWeight: An integer multiplied by base score to get bonus points.
 * </ul>
 * <p> 
 * Specification: 
 * <ul>  
 * <li> Wakeup once every WakeupInterval.
 * <li> Retrieves TypeOfCoverage.
 * <li> If coverage has improved at least PercentImprovement award BaseScoreWeight.
 * <li> If coverage has declined at least PercentImprovement deduct BaseScoreWeight.
 * </ul>
 * </p>
 * <p>
 * Incentives:
 * <ul>
 * <li> If coverage increased by at least BonusPercentImprovment for the past
 * BonusImprovedDays then award BonusScoreWeight.
 * </ul>
 * </p>
 * @author Scheller Sanchez
 *
 */
public class RewardImprovedCoverage extends Event {
  
  /** The name of the bonus. */
  private String bonusScoreName = "bonus";
  /** The last stored value for coverage. */
  private int lastCoverage = 0;
  /** Whether or not we have a prior coverage value for this team and match. */
  boolean hasLastCoverage = false;
  /** The unique name for this event. */
  public static final String eventName = "Reward Improved Coverage";
  /** The property key for the wakeup interval. */
  public static final String wakeupIntervalKey = "WakeupInterval";
  /** The actual wakeup interval. */
  private int wakeupInterval;
  /** The property key for the type of coverage. */
  public static final String typeOfCoverageKey = "TypeOfCoverage";
  /** The actual type of coverage. */
  private String typeOfCoverage;
  /** The property key for the base score weight. */
  public static final String baseScoreWeightKey = "BaseScoreWeight";
  /** The actual bonus score weight. */
  private int baseScoreWeight;
  /** The property key for the percent improvement. */
  public static final String percentImprovementKey = "percentImprovement";
  /** The actual percent improvement. */
  private int percentImprovement;
    /** The property key for Bonus Percent Improvement. */
  public static final String bonusPercentImprovementKey = "BonusPercentImprovement";
  /** The actual Bonus Percent Improvement. */
  private int bonusPercentImprovement;
  /** The property key for Bonus Improved Days. */
  public static final String bonusImprovedDaysKey = "BonusImprovedDays";
  /** The actual Bonus Improved Days.  */
  private int bonusImprovedDays;
  /** The property key for Bonus Score Weight. */
  public static final String bonusScoreWeightKey = "BonusScoreWeight";
  /** The actual Bonus Score Weight. */
  private int bonusScoreWeight;
  

  /**
   * Creates a new event for Reward Improved Coverage.
   * Ensures all required properties are available. 
   * @throws EventException If the configuration cannot be created. 
   */
  public RewardImprovedCoverage() throws EventException {
    super(eventName);
    try {
      wakeupInterval = Integer.parseInt(configManager.getProperty(eventName, wakeupIntervalKey));
      typeOfCoverage = configManager.getProperty(eventName, typeOfCoverageKey);
      baseScoreWeight = Integer.parseInt(configManager.getProperty(eventName, baseScoreWeightKey));
      percentImprovement = Integer.parseInt(configManager.getProperty
          (eventName, percentImprovementKey));
      bonusPercentImprovement = Integer.parseInt(configManager.getProperty
          (eventName, bonusPercentImprovementKey));
      bonusImprovedDays = Integer.parseInt(configManager.getProperty
          (eventName, bonusImprovedDaysKey));
      bonusScoreWeight = Integer.parseInt(configManager.getProperty
          (eventName, bonusScoreWeightKey));
    }
    catch (Exception e) {
      throw new EventException("Error configuring properties", e);
    }
  }

  /**
   * Returns the total number of points for this event. 
   * The total is the base score times the base score weight plus
   * the bonus score times the bonus score weight. 
   * @param score The event score. 
   * @return The total points for this event given the current weights. 
   */
  public int computePoints(EventScore score) {
    //return ((score.getBaseScore() * this.baseScoreWeight) +
    //    (score.getBonusScore(firstTimeBonusName) * this.firstTimeWeight));
    return ((score.getBaseScore() * this.baseScoreWeight) +
          (score.getBonusScore(this.bonusScoreName) * this.bonusScoreWeight));
  }

  /**
   * Creates an EventScore instance for RewardImprovedCoverage.  
   * @param match The match of interest. 
   * @param team The team of interest. 
   * @param timestamp the time at which this event is being run.
   * @return The EventScore.  
   * @throws EventException If problems occur while processing this event. 
   */
  public EventScore computeScore(Match match, Team team, XMLGregorianCalendar timestamp) 
  throws EventException {
    // [0] create the default score, which awards no points. 
    EventScore score = new EventScore(timestamp, match, team);
    // [1] find the project and the sensorbase client.  
    Project project = match.getProject(team);
    // [2] get the Build sensor data for the previous interval of time.
    String projectName = project.getName();
    String owner = project.getOwner();
    XMLGregorianCalendar endTime = timestamp;
    XMLGregorianCalendar startTime = Tstamp.incrementDays(endTime, wakeupInterval * -1);
    this.logger.info(String.format("Getting coverage  data for match %s and team %s",
        match.getName(), team.getName()));
    
    // [3] Retrieve the coverage for the day prior to the wakeup time. 
    DailyProjectDataClient dpdClient = match.getDailyProjectDataClient(team);
    this.logger.info(String.format("Getting DPD coverage data for match %s and team %s",
        match.getName(), team.getName()));
    CoverageDailyProjectData coverageDpd;
    try {
      coverageDpd = dpdClient.getCoverage(owner, projectName, startTime, typeOfCoverage);
    }
    catch (Exception e) {
      throw new EventException(String.format("Error getting coverage for match %s and team %s",
          match.getName(), team.getName()), e);
    }

    // [4] if no coverage since then, return the default score.
    if (!coverageDpd.hasCoverageData()) {
      score.setExplanation("Recent build, but no coverage since then, so no score.");
      return score;
    }
      
    // [5] there is coverage data, so compute the score.
    int currCoverage = coverageDpd.getPercentageCoverage();
    this.logger.info(typeOfCoverage + " coverage is: " + currCoverage);
    score.setBaseScore(computeBaseScore(score, currCoverage));
    score.addBonusScore(bonusScoreName, computeBonusScore(score, endTime, coverageDpd));
    
    // [6] now that we've computed the score, store coverage for the next time we run this event.
    this.lastCoverage = currCoverage;
    this.hasLastCoverage = true;
    // Finally return the final score. 
    return score; 
  }
  
  /**
   * Computes the base score given the current coverage. 
   * If there is no prior coverage, return 0.
   * If the current coverage is over the UpperThreshold, return 0.
   * Otherwise return between -10 and +10 depending upon change in coverage.
   * @param score The EventScore. 
   * @param currCoverage The current coverage percentage.
   * @return The base score. 
   */
  private int computeBaseScore(EventScore score, int currCoverage) {
    if (!this.hasLastCoverage) {
      score.setExplanation("No prior coverage. Base score is 0.");
      return 0;
    }
    /* 
     * The following will compare the past two coverage according to PERCENTAGE.
     * EXAMPLE: a coverage with 88 vs a coverage with 80 will return 1 because
     *          88-80 = 8 and 8 is 10 percent of an improvement.
     *          
     * if ((100 * currCoverage) > ((100 + this.percentImprovement) * lastCoverage)) {
     * score.setExplanation("Current coverage is better than last coverage. Base score is 1.");
     * return 1;
    }*/
    if ((currCoverage - lastCoverage) > (this.percentImprovement - 1)) {
      score.setExplanation("Current coverage is better than last coverage. Base score is 1.");
      return 1;
    }
    else {
      score.setExplanation("Current coverage has declined. Base score is -1");
      return -1;
    }
    
  }

  /**
   * Computes the bonus score given the current coverage. 
   * Return 1 if coverage is above the minimum percent improvement
   * for bonusImprovedDays number of days.
   * Return 0 if there is no coverage within the last 3 days.
   * Return 0 if coverage is below the minimum percent improvement.
   * Otherwise, return 1.
   * @param score The EventScore. 
   * @param endTime The end time.
   * @param coverageDpd The daily project data.
   * @return The bonus score. 
   * @throws EventException If there was no coverage data can be collected
   */
  private int computeBonusScore(EventScore score, XMLGregorianCalendar endTime,
      CoverageDailyProjectData coverageDpd) throws EventException {
    
    int currCoverage = coverageDpd.getPercentageCoverage();
    CoverageDailyProjectData coverageDpd2;
    coverageDpd2 = coverageDpd;
    XMLGregorianCalendar  bonusDay;
    
    try {
      for (int i = bonusImprovedDays; i >= 0; i--) {
        bonusDay = Tstamp.incrementDays(endTime, bonusImprovedDays * -i);
        coverageDpd2.setStartTime(bonusDay);
        int bonusCoverage = coverageDpd2.getPercentageCoverage();
        if (currCoverage - bonusCoverage < bonusPercentImprovement) {
          return 0;
        }
        score.appendExplanation("There has been improvement in coverage for three days");
        return 1;
      }
    }
    catch (Exception e) {
      throw new EventException("Error configuring properties", e);
    }
    score.appendExplanation("There has been no coverage " + bonusImprovedDays  + " days ago");
    return 0;
  }
}
